﻿ 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace SharpSoft.WPF.Controls
{
    /// <summary>
    /// 笔迹处理器，将一段笔迹分割成多个不同趋势走向的N个笔迹
    /// </summary>
    public class StrokeProcessor
    {
        /// <summary>
        /// 处理输入点
        /// </summary>
        /// <param name="points"></param>
        /// <returns></returns>
        public StrokeTendency[] Process(Point[] points)
        {
            if (points == null || points.Length == 0)
            {
                return null;
            }
            if (points.Length == 1)
            {//只有一点
                return new StrokeTendency[] { new StrokeTendency() { Points = new Point[] { points[0] }, Tendency = Tendency.None } };
            }

            List<StrokeTendency> list = new List<StrokeTendency>();
            Tendency lastT = Tendency.None;
            var lastP = points[0];
            List<Point> tempPoints = new List<Point>() {   };
            var lastAngle = 0d;
            for (int i = 1; i < points.Length; i++)
            {//忽略第一个点
                var p = points[i];
                var t = getTendency(lastP, p, out double angle);
                if (i < points.Length - 1)
                {//跳过距离过近的点，最后一点始终保留
                    var v = p - lastP;
                    if (v.Length < MinVectorLengthThreshold)
                    {
                        continue;
                    }
                }
                tempPoints.Add(p);
                if (i != 1 && i < points.Length - 1)
                {//不是第一个或最后一个点 
                    if (t != lastT && Math.Abs(angle - lastAngle) > 45)
                    {//趋势变了
                        StrokeTendency st = new StrokeTendency();
                        st.Points = tempPoints.ToArray();
                        st.Tendency = lastT;
                        list.Add(st);
                        tempPoints.Clear();
                        tempPoints.Add(p);//清空缓存点后附加当前点，当作新趋势的起点
                    }
                }
                else if (i == points.Length - 1)
                {//最后一点
                    StrokeTendency st = new StrokeTendency();
                    st.Points = tempPoints.ToArray();
                    st.Tendency = lastT;
                    list.Add(st);
                    tempPoints = null;
                }
                else
                {//第一点 
                }
                lastT = t;
                lastAngle = angle;
                lastP = p;
            }

            return list.ToArray();
        }

        /// <summary>
        /// 趋势角度阈值（以度为单位）
        /// </summary>
        public double TendencyThreshold { get; set; } = 15;
        /// <summary>
        /// 最小向量长度
        /// </summary>
        public double MinVectorLengthThreshold { get; set; } = 3;

        private Tendency getTendency(Point p1, Point p2, out double angle)
        {
            Vector vdown = new Vector(0, 1);//以正下方向量为参照
            Vector v = p2 - p1;
            angle = Vector.AngleBetween(vdown, v);
            if (angle < TendencyThreshold && angle > -TendencyThreshold)
            {//竖
                return Tendency.T2B;
            }
            else if (angle < -90 + TendencyThreshold && angle > -90 - TendencyThreshold)
            {//横
                return Tendency.L2R;
            }
            else if (angle < 90 - TendencyThreshold && angle > TendencyThreshold)
            {//撇
                return Tendency.RT2LB;
            }
            else if (angle > -90 + TendencyThreshold && angle < -TendencyThreshold)
            {//捺
                return Tendency.LT2RB;
            }
            else if (angle > -180 + TendencyThreshold && angle < -90 - TendencyThreshold)
            {//提
                return Tendency.B2T;
            }
            else
            {
                return Tendency.B2T;//其它所有笔势皆视作提
            }
        }

    }


    /// <summary>
    /// 笔迹趋势
    /// </summary>
    public class StrokeTendency
    {
        internal class Curve
        {
            public Curve(Point[] points)
            {
                this.Points = new List<Point>();
                this.Points.AddRange(points);
            }
            public List<Point> Points { get; private set; }

            /// <summary>
            /// 获取插值点
            /// </summary>
            /// <param name="tension">拉力</param>
            /// <param name="continuity">连续性</param>
            /// <param name="bias">误差</param>
            /// <param name="steplength">步长</param>
            /// <returns></returns>
            public virtual List<Point> GetInterpolatedPoints(double tension, double continuity, double bias, double steplength)
            {
                var interpolatedPoints = new List<Point>();
                if (Points.Count < 3)
                {
                    interpolatedPoints.AddRange(Points);
                }
                else if (Points.Count == 3)
                {
                    var ps = GetInterpolatedPoints(Points[0], Points[0], Points[1], Points[2], tension, continuity, bias, steplength);
                    interpolatedPoints.AddRange(ps);
                    ps = GetInterpolatedPoints(Points[0], Points[1], Points[2], Points[2], tension, continuity, bias, steplength);
                    interpolatedPoints.AddRange(ps);
                }
                else
                {
                    for (int i = 0; i < Points.Count; i++)
                    {
                        if (i == 0)
                        {
                            var point1 = this.Points[i];
                            var point2 = this.Points[i];
                            var point3 = this.Points[i + 1];
                            var point4 = this.Points[i + 2];

                            var ps = GetInterpolatedPoints(point1, point2, point3, point4, tension, continuity, bias, steplength);
                            interpolatedPoints.AddRange(ps);
                        }
                        else if (i + 2 < this.Points.Count)
                        {
                            var point1 = this.Points[i - 1];
                            var point2 = this.Points[i];
                            var point3 = this.Points[i + 1];
                            var point4 = this.Points[i + 2];

                            var ps = GetInterpolatedPoints(point1, point2, point3, point4, tension, continuity, bias, steplength);
                            interpolatedPoints.AddRange(ps);
                        }
                        else if (i + 1 < this.Points.Count)
                        {
                            var point1 = this.Points[i - 1];
                            var point2 = this.Points[i];
                            var point3 = this.Points[i + 1];
                            var point4 = this.Points[i + 1];

                            var ps = GetInterpolatedPoints(point1, point2, point3, point4, tension, continuity, bias, steplength);
                            interpolatedPoints.AddRange(ps);
                        }
                    }
                }


                return interpolatedPoints;
            }
            private static List<Point> GetInterpolatedPoints(Point point1, Point point2, Point point3, Point point4, double tension, double continuity, double bias, double steplength)
            {
                var interpolatedPoints = new List<Point>();

                var v = point3 - point2;
                var vlength = v.Length;
                for (var i = 0d; i <= vlength; i += steplength)
                {//TODO:将使用steps均分改成使用steplength将每段分成等长的N段
                    var s = i / vlength;

                    var h1 = 2 * Math.Pow(s, 3) - 3 * Math.Pow(s, 2) + 1;
                    var h2 = (-2) * Math.Pow(s, 3) + 3 * Math.Pow(s, 2);
                    var h3 = Math.Pow(s, 3) - 2 * Math.Pow(s, 2) + s;
                    var h4 = Math.Pow(s, 3) - Math.Pow(s, 2);

                    var tDix = (1 - tension) * (1 + continuity) * (1 + bias) * (point2.X - point1.X) / 2 + (1 - tension) * (1 - continuity) * (1 - bias) * (point3.X - point2.X) / 2;
                    var tDiy = (1 - tension) * (1 + continuity) * (1 + bias) * (point2.Y - point1.Y) / 2 + (1 - tension) * (1 - continuity) * (1 - bias) * (point3.Y - point2.Y) / 2;

                    var tSix = (1 - tension) * (1 - continuity) * (1 + bias) * (point3.X - point2.X) / 2 + (1 - tension) * (1 + continuity) * (1 - bias) * (point4.X - point3.X) / 2;
                    var tSiy = (1 - tension) * (1 - continuity) * (1 + bias) * (point3.Y - point2.Y) / 2 + (1 - tension) * (1 + continuity) * (1 - bias) * (point4.Y - point3.Y) / 2;

                    var x = h1 * point2.X + h2 * point3.X + h3 * tDix + h4 * tSix;
                    var y = h1 * point2.Y + h2 * point3.Y + h3 * tDiy + h4 * tSiy;

                    interpolatedPoints.Add(new Point((int)x, (int)y));
                }

                interpolatedPoints.Add(point3);

                return interpolatedPoints;
            }
        }

        /// <summary>
        /// 原始点
        /// </summary>
        public Point[] Points { get; set; }

        /// <summary>
        /// 趋势
        /// </summary>
        public Tendency Tendency { get; set; }


        /// <summary>
        /// 获取曲线插值点
        /// </summary>
        public Point[] GetInterpolatedPoints(double steplength = 4)
        {
            if (this.Points == null)
            {
                throw new Exception("笔迹趋势的原始点不能为NULL。");
            }
            var point2s = new Point[Points.Length];
            for (int i = 0; i < this.Points.Length; i++)
            {
                var p = this.Points[i];
                point2s[i] = new Point(p.X, p.Y);
            }
            Curve curve = new Curve(point2s.ToArray());
            var ips = curve.GetInterpolatedPoints(0, 0, 0, steplength);//计算插值曲线 

            var points = new Point[ips.Count];
            for (int i = 0; i < ips.Count; i++)
            {
                var p = ips[i];
                points[i] = new Point(p.X, p.Y);
            }
            return points;
        }

        /// <summary>
        /// 获取笔画粗细
        /// </summary> 
        /// <param name="pcount">点数</param>
        /// <returns></returns>
        public double[] GetWeights(int pcount)
        {
            double[] weights = new double[pcount];
            Dictionary<double, double> dic = new Dictionary<double, double>();
            switch (Tendency)
            {
                case Tendency.L2R://横
                    dic.Add(0, 0.7);
                    dic.Add(0.1, 0.6);
                    dic.Add(0.5, 0.5);
                    dic.Add(0.8, 0.7);
                    dic.Add(1, 0.2);
                    break;
                case Tendency.B2T://提
                    dic.Add(0, 0.8);
                    dic.Add(0.1, 0.7);
                    dic.Add(0.8, 0.3);
                    dic.Add(1, 0);
                    break;
                case Tendency.T2B://竖
                    dic.Add(0, 0.65);
                    dic.Add(0.1, 0.60);
                    dic.Add(0.6, 0.67);
                    dic.Add(0.95, 0.5);
                    dic.Add(1, 0.2);
                    break;
                case Tendency.RT2LB://撇
                    dic.Add(0, 0.8);
                    dic.Add(0.1, 0.7);
                    dic.Add(0.8, 0.3);
                    dic.Add(0.9, 0.2);
                    dic.Add(1, 0.1);
                    break;
                case Tendency.LT2RB://捺
                    dic.Add(0, 0.8);
                    dic.Add(0.1, 0.7);
                    dic.Add(0.4, 0.8);
                    dic.Add(0.5, 0.9);
                    dic.Add(0.7, 1.1);
                    dic.Add(0.97, 0.2);
                    dic.Add(1, 0.1);
                    break;
                case Tendency.None:
                default:
                    for (int i = 0; i < pcount; i++)
                    {
                        weights[i] = 0.7;
                    }
                    return weights;
            }
            var env = dic.GetEnumerator();
            if (env.MoveNext())
            {
                while (true)
                {
                    var curP = env.Current.Key;
                    var curW = env.Current.Value;

                    if (env.MoveNext())
                    {

                        var nextVL = env.Current.Key;
                        var nextPF = env.Current.Value;

                        var i1 = curP * (pcount - 1);
                        var i2 = nextVL * (pcount - 1);
                        var startindex = (int)(i1);
                        var endindex = (int)(i2);
                        var weightStepLength = (nextPF - curW) / (endindex - startindex);
                        for (int i = startindex; i < endindex; i++)
                        {
                            weights[i] = curW + (i - startindex) * weightStepLength;
                        }
                    }
                    else
                    {
                        break;
                    }

                }
            }
            return weights;
        }
    }

    public enum Tendency
    {
        /// <summary>
        /// 只有一个点
        /// </summary>
        None,
        /// <summary>
        /// 从左往右的横
        /// </summary>
        L2R,
        /// <summary>
        /// 从下往上的提
        /// </summary>
        B2T,
        /// <summary>
        /// 从上往下的竖
        /// </summary>
        T2B,
        /// <summary>
        /// 从右上到左下的撇
        /// </summary>
        RT2LB,
        /// <summary>
        /// 从左上到右下的捺
        /// </summary>
        LT2RB


    }

   
}
